;----------------------------------------
;04_page_swith.asm
;该工程参照《自己动手写操作系统》pmtest8
;主题是在保护模式下初始化页表，更改页表映射并观察分页机制的效果
;----------------------------------------
%include "pm.inc"


;现假设我们要弄两组的映射
PageDirBase0 equ 200000h
PageTblBase0 equ 201000h
PageDirBase1 equ 210000h
PageTblBase1 equ 211000h

;预设两个物理地址不同的函数，然后将其映射到相同的虚拟地址
LineAddrDemo equ 00401000h
ProcFoo equ 00401000h
ProcBar equ 00501000h
ProcPagingDemo equ 00301000h

;程序正式开始
org 0100h
jmp LABEL_BEGIN

;GDT表，此处为了将执行程序和修改程序同时进行，设置了两个权限互斥的4G的段
[SECTION .gdt]
LABEL_GDT Descriptor 0,0,0
LABEL_DESC_CODE32 Descriptor 0,SegCode32Len-1,DA_CR|DA_32
LABEL_DESC_STACK32 Descriptor 0,TopOfStack,DA_DRWA|DA_32
LABEL_DESC_CODE16 Descriptor 0,0ffffh,DA_C
LABEL_DESC_FLAT4G_RW Descriptor 0,0fffffh,DA_DRW|DA_LIMIT_4K
LABEL_DESC_FLAT4G_EX Descriptor 0,0fffffh,DA_CR|DA_32|DA_LIMIT_4K
LABEL_DESC_NORMAL Descriptor 0,0ffffh,DA_DRW
LABEL_DESC_DATA Descriptor 0,DataLen-1,DA_DRW
LABEL_DESC_VIDEO Descriptor 0B8000h,0ffffh,DA_DRW

GdtLen equ $-LABEL_GDT
GdtPtr dw GdtLen-1
			dd 0

;选择子
SelectorCode32  equ LABEL_DESC_CODE32 - LABEL_GDT
SelectorCode16  equ LABEL_DESC_CODE16 - LABEL_GDT
SelectorStack32 equ LABEL_DESC_STACK32 - LABEL_GDT
SelectorFlatRw  equ LABEL_DESC_FLAT4G_RW - LABEL_GDT
SelectorFlatEx  equ LABEL_DESC_FLAT4G_EX - LABEL_GDT
SelectorData  equ LABEL_DESC_DATA - LABEL_GDT
SelectorVideo  equ LABEL_DESC_VIDEO - LABEL_GDT
SelectorNormal  equ LABEL_DESC_NORMAL - LABEL_GDT

[SECTION .data]
ALIGN 32
[BITS 32]
LABEL_DATA:
_szPMMessage:   db "In Protect Mode now. ^-^", 0Ah, 0Ah, 0 ; 
_szMemChkTitle:   db "BaseAddrL BaseAddrH LengthLow LengthHigh   Type", 0Ah, 0
_szRAMSize   db "RAM size:", 0
_szReturn   db 0Ah, 0
_wSPValueInRealMode  dw 0
_dwMCRNumber:   dd 0 ; Memory Check Result
_dwDispPos:   dd (80 * 6 + 0) * 2 ; 屏幕第 6 行, 第 0 列。
_dwMemSize:   dd 0
_ARDStruct:   ; Address Range Descriptor Structure
 _dwBaseAddrLow:  dd 0
 _dwBaseAddrHigh: dd 0
 _dwLengthLow:  dd 0
 _dwLengthHigh:  dd 0
 _dwType:  dd 0
_PageTableNumber  dd 0

_MemChkBuf: times 256 db 0

; 保护模式下使用这些符号
szPMMessage  equ _szPMMessage - $$
szMemChkTitle  equ _szMemChkTitle - $$
szRAMSize  equ _szRAMSize - $$
szReturn  equ _szReturn - $$
dwDispPos  equ _dwDispPos - $$
dwMemSize  equ _dwMemSize - $$
dwMCRNumber  equ _dwMCRNumber - $$
ARDStruct  equ _ARDStruct - $$
 dwBaseAddrLow equ _dwBaseAddrLow - $$
 dwBaseAddrHigh equ _dwBaseAddrHigh - $$
 dwLengthLow equ _dwLengthLow - $$
 dwLengthHigh equ _dwLengthHigh - $$
 dwType  equ _dwType  - $$
MemChkBuf  equ _MemChkBuf - $$
PageTableNumber  equ _PageTableNumber- $$

DataLen   equ $ - LABEL_DATA

; 全局堆栈段
[SECTION .gs]
ALIGN 32
[BITS 32]
LABEL_STACK32:
 times 512 db 0
TopOfStack equ $ - LABEL_STACK32 - 1

;16位代码段，包括初始化分段机制的代码，加载GDT的代码，和返回实模式的代码
[SECTION .s16]
[BITS 16]
LABEL_BEGIN:
;jmp $
;实模式下访问各个段
mov ax,cs
mov ds,ax
mov es,ax
mov ss,ax
mov sp,0100h

;修改代码段，记录实模式栈顶
mov [LABEL_GO_BACK_TO_REAL+3], ax
mov [_wSPValueInRealMode], sp

;开始检索内存
mov ebx,0
mov di,_MemChkBuf
.loop:
	mov eax,0E820h
	mov ecx,20
	mov edx,0534D4150h
	int 15h
	jc LABEL_MEM_CHK_FAIL
	add di,20
	inc dword [_dwMCRNumber]
	cmp ebx,0
jne .loop
jmp LABEL_MEM_CHK_OK
LABEL_MEM_CHK_FAIL:
mov dword [_dwMCRNumber], 0
LABEL_MEM_CHK_OK:

mov ax, cs
movzx eax, ax
shl eax, 4
add eax, LABEL_SEG_CODE16
mov word [LABEL_DESC_CODE16 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_CODE16 + 4], al
mov byte [LABEL_DESC_CODE16 + 7], ah

;初始化段描述符
xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_SEG_CODE32
mov word [LABEL_DESC_CODE32+2],ax
shr eax,16
mov byte [LABEL_DESC_CODE32+4],al
mov byte [LABEL_DESC_CODE32+7],ah

xor eax,eax
mov ax,cs
shl eax,4
add eax,LABEL_DATA
mov word [LABEL_DESC_DATA+2],ax
shr eax,16
mov byte [LABEL_DESC_DATA+4],al
mov byte [LABEL_DESC_DATA+7],ah

xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_STACK32
mov word [LABEL_DESC_STACK32 + 2], ax
shr eax, 16
mov byte [LABEL_DESC_STACK32 + 4], al
mov byte [LABEL_DESC_STACK32 + 7], ah



;加载GTD
xor eax, eax
mov ax, cs
shl eax, 4
add eax, LABEL_GDT
mov dword [GdtPtr + 2], eax
lgdt [GdtPtr]

;关中断
cli

;打开A20地址线，扩大寻址
in al,92h
or al,00000010b
out 92h,al

;设置cr0
mov eax,cr0
or eax,1
mov cr0,eax

;进入保护模式
jmp dword SelectorCode32:0

LABEL_REAL_ENTRY:  ; 从保护模式跳回到实模式就到了这里
;还原实模式状态
mov ax, cs
mov ds, ax
mov es, ax
mov ss, ax
mov sp, [_wSPValueInRealMode]

;关闭A20地址线
in al, 92h 
and al, 11111101b 
out 92h, al 

;开中断
sti

;回到DOS
mov ax, 4c00h
int 21h

;32位代码段，在保护模式中，我们开启分页模式
[SECTION .s32]
[BITS 32]

LABEL_SEG_CODE32:
;初始化各个段寄存器以适应保护模式
mov ax,SelectorData
mov ds,ax
mov es,ax
mov ax,SelectorVideo
mov gs,ax
mov ax,SelectorStack32
mov ss,ax
mov esp,TopOfStack

push szPMMessage
call DispStr
add esp,4

push szMemChkTitle
call DispStr
add esp,4

;开始实验进程
call DispMemSize
call PagingDemo

;返回实模式
jmp SelectorCode16:0

;启动分页机制
SetupPaging:
;计算页表个数
xor edx,edx
mov eax,[dwMemSize]
mov ebx,400000h
div ebx
mov ecx,eax
test edx,edx
jz .no_remain
inc ecx
.no_remain:
mov [PageTableNumber], ecx

;初始化页目录，并将页表都设成存在，可读可写
mov ax,SelectorFlatRw
mov es,ax
mov edi,PageDirBase0
xor eax,eax
mov eax,PageTblBase0 | PG_P | PG_USU | PG_RWW
.1:
	stosd
	add eax,4096
loop .1

;初始化在页目录中的页表
mov edi,PageTblBase0
;计算有多少个页表项
mov eax,[PageTableNumber]
mov ebx,1024
mul ebx
mov ecx,eax
xor eax,eax
mov eax,PG_P  | PG_USU | PG_RWW
.2:
	stosd
	add eax,4096
loop .2

mov eax,PageDirBase0
mov cr3,eax
mov eax,cr0
or eax,80000000h
mov cr0,eax
jmp short .3
.3:
	nop
ret

PagingDemo:
mov ax,cs
mov ds,ax
mov ax,SelectorFlatRw
mov es,ax

;将Foo的代码拷贝到ProcFoo处
push LenFoo
push OffsetFoo
push ProcFoo
call MemCpy
add esp, 12

;将Bar的代码拷贝到ProcBar处
push LenBar
push OffsetBar
push ProcBar
call MemCpy
add esp, 12

;将PagingDemo拷贝到ProcPagingDemo处
push LenPagingDemoAll
push OffsetPagingDemoProc
push ProcPagingDemo
call MemCpy                  
add esp, 12

mov ax, SelectorData           ;0x0030
mov ds, ax   ; 数据段选择子
mov es, ax

call SetupPaging
call SelectorFlatEx:ProcPagingDemo
call PageSwitch
call SelectorFlatEx:ProcPagingDemo
ret

;下面进行页表切换，所谓页表切换，就是改变线性地址到物理地址的映射关系
PageSwitch:
mov ax,SelectorFlatRw
mov es,ax
mov edi,PageDirBase1
xor eax,eax
mov eax, PageTblBase1 | PG_P | PG_USU | PG_RWW
mov ecx,[PageTableNumber]
.1:
	stosd
	add eax,4096
loop .1
mov eax,[PageTableNumber]
mov ebx,1024
mul ebx
mov ecx,eax
mov edi,PageTblBase1
xor eax,eax
mov eax, PG_P | PG_USU | PG_RWW
.2:
	stosd
	add eax,4096
loop .2
xchg bx,bx

mov eax, LineAddrDemo
shr eax, 22
mov ebx,4096
mul ebx
mov ecx,eax
mov eax,LineAddrDemo
shr eax,12
and eax,03ffh
mov ebx,4
mul ebx
add eax,ecx
add eax,PageTblBase1
mov dword [es:eax], ProcBar | PG_P | PG_USU | PG_RWW

mov eax, PageDirBase1
mov cr3, eax
jmp short .3
.3:
	nop
ret

PagingDemoProc:
OffsetPagingDemoProc equ PagingDemoProc - $$
mov eax, LineAddrDemo
call eax               ;跳到foo处执行代码
retf
LenPagingDemoAll equ $ - PagingDemoProc

foo:
OffsetFoo  equ foo - $$
mov ah, 0Ch   ; 0000: 黑底    1100: 红字
mov al, 'F'
mov [gs:((80 * 17 + 0) * 2)], ax ; 屏幕第 17 行, 第 0 列。
mov al, 'o'
mov [gs:((80 * 17 + 1) * 2)], ax ; 屏幕第 17 行, 第 1 列。
mov [gs:((80 * 17 + 2) * 2)], ax ; 屏幕第 17 行, 第 2 列。
ret
LenFoo   equ $ - foo

bar:
OffsetBar  equ bar - $$
mov ah, 0Ch   ; 0000: 黑底    1100: 红字
mov al, 'B'
mov [gs:((80 * 18 + 0) * 2)], ax ; 屏幕第 18 行, 第 0 列。
mov al, 'a'
mov [gs:((80 * 18 + 1) * 2)], ax ; 屏幕第 18 行, 第 1 列。
mov al, 'r'
mov [gs:((80 * 18 + 2) * 2)], ax ; 屏幕第 18 行, 第 2 列。
ret
LenBar   equ $ - bar

;显示内存信息（嵌套循环）
DispMemSize:
 push esi
 push edi
 push ecx

 mov esi, MemChkBuf
 mov ecx, [dwMCRNumber] ;for(int i=0;i<[MCRNumber];i++) // 每次得到一个ARDS(Address Range Descriptor Structure)结构
.loop:     ;{
 mov edx, 5   ; for(int j=0;j<5;j++) // 每次得到一个ARDS中的成员，共5个成员
 mov edi, ARDStruct  ; {   // 依次显示：BaseAddrLow，BaseAddrHigh，LengthLow，LengthHigh，Type
.1:     ;
 push dword [esi]  ;
 call DispInt   ;  DispInt(MemChkBuf[j*4]); // 显示一个成员
 pop eax   ;
 stosd    ;  ARDStruct[j*4] = MemChkBuf[j*4];
 add esi, 4   ;
 dec edx   ;
 cmp edx, 0   ;
 jnz .1   ; }
 call DispReturn  ; printf("\n");
 cmp dword [dwType], 1 ; if(Type == AddressRangeMemory) // AddressRangeMemory : 1, AddressRangeReserved : 2
 jne .2   ; {
 mov eax, [dwBaseAddrLow] ;
 add eax, [dwLengthLow] ;
 cmp eax, [dwMemSize] ;  if(BaseAddrLow + LengthLow > MemSize)
 jb .2   ;
 mov [dwMemSize], eax ;   MemSize = BaseAddrLow + LengthLow;
.2:     ; }
 loop .loop   ;}
     ;
 call DispReturn  ;printf("\n");
 push szRAMSize  ;
 call DispStr   ;printf("RAM size:");
 add esp, 4   ;
     ;
 push dword [dwMemSize] ;
 call DispInt   ;DispInt(MemSize);
 add esp, 4   ;

 pop ecx
 pop edi
 pop esi
 ret

%include "lib.inc"
 SegCode32Len equ $ - LABEL_SEG_CODE32
; END of [SECTION .s32]

; 16 位代码段. 由 32 位代码段跳入, 跳出后到实模式
[SECTION .s16code]
ALIGN 32
[BITS 16]
LABEL_SEG_CODE16:
 ; 跳回实模式:
 mov ax, SelectorNormal
 mov ds, ax
 mov es, ax
 mov fs, ax
 mov gs, ax
 mov ss, ax

 mov eax, cr0
 and     eax, 7FFFFFFEh          ; PE=0, PG=0
 mov cr0, eax

LABEL_GO_BACK_TO_REAL:
 jmp 0:LABEL_REAL_ENTRY ; 段地址会在程序开始处被设置成正确的值

Code16Len equ $ - LABEL_SEG_CODE16

; END of [SECTION .s16code]